Skip to content

Add SDK MCP OAuth host token handlers#1669

Draft
roji wants to merge 1 commit into
mainfrom
roji/roji-mcp-auth-sdk
Draft

Add SDK MCP OAuth host token handlers#1669
roji wants to merge 1 commit into
mainfrom
roji/roji-mcp-auth-sdk

Conversation

@roji

@roji roji commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Sync SDK generated RPC/session-event surfaces to the runtime MCP OAuth lifecycle contract: required reason, optional wwwAuthenticateParams.resourceMetadataUrl, optional raw resourceMetadata, and no host refreshToken in token responses.
  • Thread MCP OAuth auth request handling through Node, Python, Go, .NET, Java, and Rust public callback APIs.
  • Preserve event-interest behavior: SDKs register mcp.oauth_required interest only when the high-level MCP auth callback is configured.
  • Expand shared OAuth-protected MCP fixture and per-language E2E coverage for host-delegated initial auth, refresh/replacement, upscope, reauth, cancellation, and structured challenge propagation (scope, resourceMetadataUrl, error=insufficient_scope, and refresh error=invalid_token).

Notes

  • A separate temporary commit (Use local runtime for MCP OAuth E2E validation) points E2E harnesses at /Users/roji/.copilot/repos/copilot-worktrees/copilot-agent-runtime/roji-symmetrical-dollop/dist-cli/index.js for runtime PR #11277 validation. This should be removed before final merge.
  • The deterministic lifecycle E2E path now covers initial -> refresh -> upscope -> refresh-cancel -> reauth across all supported SDK languages.
  • The prior Node session.mcp.reloadWithConfig workaround was removed; Node E2E passes without it against the current runtime branch.

Validation

  • cd test/harness && npm ci
  • cd nodejs && npm run typecheck && npx vitest run test/e2e/mcp_oauth.e2e.test.ts
  • cd python && uv run pytest e2e/test_mcp_oauth_e2e.py
  • cd go && go test ./internal/e2e -run TestMCPOAuthE2E -count=1
  • cd dotnet && DOTNET_ROLL_FORWARD=Major dotnet test test/GitHub.Copilot.SDK.Test.csproj --filter FullyQualifiedName~McpOAuthE2ETests
  • cd rust && export PATH="$HOME/.cargo/bin:$PATH" && cargo test --features test-support --test e2e mcp_oauth -- --nocapture
  • cd java && mvn test -Dtest=McpOAuthE2ETest
  • Formatting: gofmt -w internal/e2e/mcp_oauth_e2e_test.go, cargo fmt -- tests/e2e/mcp_oauth.rs, and mvn spotless:apply -Dspotless.check.skip=false.

@github-actions github-actions Bot added the dependencies Pull requests that update a dependency file label Jun 14, 2026
Comment thread python/copilot/client.py Fixed
Comment thread python/copilot/generated/rpc.py Fixed
@github-actions

This comment has been minimized.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by SDK Consistency Review Agent for issue #1669 · sonnet46 2.1M

Comment thread rust/src/types.rs Outdated
Comment thread dotnet/src/Session.cs Fixed
Comment thread nodejs/src/types.ts
Comment thread nodejs/src/types.ts
Comment on lines +1585 to +1594
export interface McpAuthToken {
/** Access token acquired by the SDK host. */
accessToken: string;
/** OAuth token type. Defaults to Bearer when omitted. */
tokenType?: string;
/** Refresh token supplied by the host, if available. */
refreshToken?: string;
/** Token lifetime in seconds, if known. */
expiresIn?: number;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about an idToken? idTokens are useful as they contain claims that are good to display like usernames/email/etc.

@roji roji Jun 26, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So unless I'm mistaken, the runtime wouldn't have any actual use for it; all it needs as a response from the host is the access token to be forwarded back to the MCP server for access... In fact, I'm about to remove the refreshToken from here, since as discussed in yesterday's meeting, refresh would be the host's responsibility, and so the runtime has no use for that.

But let me know if I have the wrong mental model here! Plus we can always add it later.

@roji roji force-pushed the roji/roji-mcp-auth-sdk branch from 59694fd to 6d3d655 Compare June 24, 2026 10:28
Comment thread java/src/main/java/com/github/copilot/CopilotClient.java Fixed
Comment thread java/src/main/java/com/github/copilot/CopilotClient.java Fixed
Comment thread java/src/main/java/com/github/copilot/CopilotClient.java Fixed
Comment thread java/src/main/java/com/github/copilot/rpc/McpAuthHandler.java Fixed
@github-actions

This comment has been minimized.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by SDK Consistency Review Agent for issue #1669 · sonnet46 1.8M

Comment thread rust/src/types.rs Outdated
Comment thread java/src/test/java/com/github/copilot/McpOAuthE2ETest.java Fixed
@github-actions

This comment has been minimized.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by SDK Consistency Review Agent for issue #1669 · sonnet46 3.1M

Comment thread dotnet/src/Types.cs
Comment thread dotnet/test/E2E/McpOAuthE2ETests.cs Fixed
Comment thread dotnet/test/E2E/McpOAuthE2ETests.cs Fixed
@roji roji force-pushed the roji/roji-mcp-auth-sdk branch from a4cfe07 to e3aae80 Compare June 26, 2026 15:18
@github-actions

This comment has been minimized.

@roji roji force-pushed the roji/roji-mcp-auth-sdk branch from 530ce4b to a16180e Compare June 26, 2026 15:47
@github-actions

This comment has been minimized.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by SDK Consistency Review Agent for issue #1669 · sonnet46 2.7M

Comment thread go/types.go
Comment thread go/types.go Outdated
Comment thread python/copilot/session.py
@roji roji force-pushed the roji/roji-mcp-auth-sdk branch from 1ef3726 to 87f1785 Compare June 29, 2026 12:52
Comment thread java/src/main/java/com/github/copilot/CopilotClient.java Fixed
Comment thread java/src/main/java/com/github/copilot/CopilotClient.java Fixed
@github-actions

This comment has been minimized.

@roji roji force-pushed the roji/roji-mcp-auth-sdk branch 4 times, most recently from 51c9235 to d5c709b Compare June 29, 2026 13:40
Expose host-delegated MCP OAuth handling across SDK languages, sync generated RPC and event models to the lifecycle contract, and add cross-language E2E coverage for initial auth, refresh, upscope, reauth, and cancellation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@roji roji force-pushed the roji/roji-mcp-auth-sdk branch from d5c709b to 70ec1fd Compare June 29, 2026 14:05
@github-actions

Copy link
Copy Markdown
Contributor

Cross-SDK Consistency Review

This PR successfully threads MCP OAuth host token handler support through all six SDK implementations. The wire-format types and event-interest registration logic are well-aligned across the board. One consistency finding is worth addressing before merge.


⚠️ Java McpAuthHandler deviates from the established Java handler pattern

Every existing Java handler in the SDK uses a two-parameter signature separating server-side request data from SDK-layer session context:

Handler Signature
PermissionHandler handle(PermissionRequest request, PermissionInvocation invocation)
UserInputHandler handle(UserInputRequest request, UserInputInvocation invocation)
ExitPlanModeHandler handle(ExitPlanModeRequest request, ExitPlanModeInvocation invocation)

This two-parameter pattern (request + invocation) also matches Node.js, Python, and Go:

  • Node.js: (request: McpAuthRequest, context: { sessionId: string })
  • Python: (request: McpAuthRequest, context: McpAuthContext)
  • Go: (request MCPAuthRequest, invocation MCPAuthInvocation)

The new McpAuthHandler instead uses a single-parameter signature with sessionId embedded directly in McpAuthRequest:

// Current (inconsistent)
public record McpAuthRequest(String sessionId, String requestId, String serverName, ...)
CompletableFuture<McpAuthResult> handle(McpAuthRequest request);

This is unusual because sessionId is SDK-layer context, not a field from the MCP server — it doesn't belong in a type called McpAuthRequest.

Suggested fix — follow the established Java SDK pattern:

// Add a companion invocation class
public final class McpAuthInvocation {
    private String sessionId;
    // getter/setter
}

// Remove sessionId from McpAuthRequest record
public record McpAuthRequest(String requestId, String serverName, String serverUrl,
        McpOauthRequestReason reason, ...)

// Update handler signature
CompletableFuture<McpAuthResult> handle(McpAuthRequest request, McpAuthInvocation invocation);

Alternatively, following the ElicitationHandler pattern (single merged context class), McpAuthRequest could be renamed to McpAuthContext — though that would make it the only context-style handler that bundles requestId, which is slightly unusual.


✅ Everything else is consistent

  • .NET uses McpAuthContext (merging all fields into a single context) — consistent with .NET's own ElicitationContext pattern.
  • Rust exposes session_id and request_id as strongly-typed separate parameters — consistent with Rust's own PermissionHandler approach.
  • All six SDKs register mcp.oauth_required event interest only when the handler is configured.
  • Wire format types (reason, wwwAuthenticateParams, resourceMetadata, staticClientConfig, accessToken, tokenType, expiresIn) are structurally equivalent across all SDKs.

Generated by SDK Consistency Review Agent for issue #1669 · sonnet46 2.3M ·

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by SDK Consistency Review Agent for issue #1669 · sonnet46 2.3M

*
* @since 1.0.0
*/
public record McpAuthRequest(String sessionId, String requestId, String serverName, String serverUrl,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Including sessionId in McpAuthRequest mixes SDK-layer session context with server-side request data, which is inconsistent with the established Java SDK pattern.\n\nEvery other Java handler in this SDK separates these concerns via a companion XxxInvocation type:\n- PermissionHandler: handle(PermissionRequest request, PermissionInvocation invocation)\n- UserInputHandler: handle(UserInputRequest request, UserInputInvocation invocation)\n- ExitPlanModeHandler: handle(ExitPlanModeRequest request, ExitPlanModeInvocation invocation)\n\nThis two-parameter pattern (request + invocation) also matches Node.js, Python, and Go, where sessionId is always in a separate context/invocation argument rather than bundled into the request object.\n\nSuggested: add a McpAuthInvocation class with sessionId (following PermissionInvocation), remove sessionId from this record, and update McpAuthHandler.handle to handle(McpAuthRequest request, McpAuthInvocation invocation).

* the MCP OAuth request context
* @return a future resolving to token data or cancellation
*/
CompletableFuture<McpAuthResult> handle(McpAuthRequest request);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This single-parameter signature deviates from the two-parameter pattern used by all other Java handlers in the SDK:\n\njava\n// All other Java handlers:\nCompletableFuture<PermissionRequestResult> handle(PermissionRequest request, PermissionInvocation invocation);\nCompletableFuture<UserInputResponse> handle(UserInputRequest request, UserInputInvocation invocation);\nCompletableFuture<ExitPlanModeResult> handle(ExitPlanModeRequest request, ExitPlanModeInvocation invocation);\n\n\nNode.js, Python, and Go also pass session context as a second argument (separate from the request). Consider updating this to:\n\njava\nCompletableFuture<McpAuthResult> handle(McpAuthRequest request, McpAuthInvocation invocation);\n\n\nwith McpAuthInvocation holding sessionId and McpAuthRequest containing only the MCP-server-originated fields (requestId, serverName, serverUrl, reason, etc.).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants